﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Collections;
using System.Web;
using System.Net;
using System.IO;
using System.Security.Cryptography;

namespace LFM_Playlist_Builder
{
    class LastFMXML
    {
        XmlDocument lastXML = new XmlDocument();
        HashSet<String> artists = new HashSet<String>();

        String lastPath = "";

        String username = "";
        int trackCount = 50;
        int mode = 0;

        public LastFMXML()
        {

        }

        public LastFMXML(String iUsername, int iTrackCount, int iMode)
        {
            username = iUsername;
            trackCount = iTrackCount;
            mode = iMode;
            reloadPath();
        }

        public LastFMXML(String iUsername, int iTrackCount, int iMode, HashSet<String> iArtists)
        {
            username = iUsername;
            trackCount = iTrackCount;
            mode = iMode;
            artists = iArtists;
            reloadPath();
        }

        public void reloadPath()
        {
            switch (mode)
            {
                case 0:
                    lastPath = ("http://ws.audioscrobbler.com/2.0/?method=user.getLovedTracks&user=" + username + "&limit=" + trackCount + "&api_key=" + Global.LASTKEY);
                    break;
                case 1:
                    lastPath = ("http://ws.audioscrobbler.com/2.0/?method=user.getRecommendedArtists");
                    break;
                default:
                    lastPath = ("http://ws.audioscrobbler.com/2.0/?method=user.getLovedTracks&user=" + username + "&limit=" + trackCount + "&api_key=" + Global.LASTKEY);
                    break;
            }
        }

        /* Sets the path for the XML reader. Returns true to set correctly, false for error
         * 
         */
        public int load(int page)
        {
            try
            {
                switch(mode)
                {
                    case 0:
                        lastXML.Load(lastPath + "&page=" + page);
                        break;
                    case 1:
                        //First load artists
                        lastXML.Load(lastPath + "&limit=" + (int)Math.Sqrt(trackCount) + "&api_key=" + Global.LASTKEY + "&sk=" + Properties.Settings.Default.sessionKey + "&page=" + page +"&api_sig=" +
                            calculateMD5Hash("api_key" + Global.LASTKEY + "limit" + (int)Math.Sqrt(trackCount) + "methoduser.getRecommendedArtists" + "page" + page + "sk" + Properties.Settings.Default.sessionKey + Global.SECRET));
                        //Then load top tracks for each artist
                        break;
                    case 2:
                        //Don't really need to do anything
                        break;
                    default:
                        lastXML.Load(lastPath + "&page=" + page);
                        break;
                }
            }
            catch (XmlException e)
            {
                if (e.Message == "403")
                {
                    return Global.USERAUTHERROR;
                }
                else if (e.Message == "Timeout")
                {
                    return Global.LOADERROR;
                }
                return Global.MISCERROR;
            }
            catch (Exception e)
            {
                if (e.Message == "The remote server returned an error: (403) Forbidden.")
                {
                    return Global.AUTHERROR;
                }

                return Global.MISCERROR;
            }

            return Global.OK;
        }

        public void setTrackCount(int iTrackCount)
        {
            trackCount = iTrackCount;
        }

        public int getTrackCount()
        {
            return trackCount;
        }

        public ArrayList getTracks()
        {
            ArrayList trackArray = new ArrayList();
            XmlNodeList lastList = lastXML.GetElementsByTagName("track");
            for (int i = 0; i < lastList.Count; i++)
            {
                trackArray.Add(HttpUtility.HtmlDecode(lastList[i]["artist"]["name"].InnerXml) + " - " + HttpUtility.HtmlDecode(lastList[i]["name"].InnerXml));
            }
            return trackArray;
        }

        public List<TrackInfo> getTrackInfo(int page)
        {
            switch (mode)
            {
                case 0:
                    return getLovedTrackInfo();
                case 1:
                    return getRecTrackInfo(page);
                case 2:
                    return getLovedATrackInfo(page);
                default:
                    return getLovedTrackInfo();
            }
        }

        public List<TrackInfo> getLovedTrackInfo()
        {
            List<TrackInfo> trackArray = new List<TrackInfo>();
            XmlNodeList lastList = lastXML.GetElementsByTagName("track");
            for (int i = 0; i < lastList.Count; i++)
            {
                trackArray.Add(new TrackInfo(HttpUtility.HtmlDecode(lastList[i]["artist"]["name"].InnerXml), HttpUtility.HtmlDecode(lastList[i]["name"].InnerXml), "", "", trackArray.Count, Global.MLOVED));
                artists.Add(lastList[i]["artist"]["name"].InnerXml);
            }

            return trackArray;
        }

        public List<TrackInfo> getRecTrackInfo(int page)
        {
            List<TrackInfo> trackArray = new List<TrackInfo>();
            XmlNodeList artistList = lastXML.GetElementsByTagName("artist");
            for (int i = 0; i < artistList.Count; i++)
            {
                //Get the top tracks for each artist
                XmlDocument artistXML = new XmlDocument();
                try
                {
                    artistXML.Load("http://ws.audioscrobbler.com/2.0/?method=artist.gettoptracks&artist=" + artistList[i]["name"].InnerXml
                        + "&limit=" + (int)Math.Sqrt(trackCount) + "&page=" + page + "&api_key=" + Global.LASTKEY);
                    XmlNodeList lastList = artistXML.GetElementsByTagName("track");
                    for (int j = 0; j < artistList.Count; j++)
                    {
                        trackArray.Add(new TrackInfo(HttpUtility.HtmlDecode(artistList[i]["name"].InnerXml), HttpUtility.HtmlDecode(lastList[j]["name"].InnerXml), "", "", trackArray.Count, Global.MREC));
                    }
                }
                catch (Exception ex)
                {
                    return trackArray; //Just break, don't want to miss all the lovely tracks we have already found
                }
            }
            return trackArray;
        }

        public List<TrackInfo> getLovedATrackInfo(int page)
        {
            List<TrackInfo> trackArray = new List<TrackInfo>();
            foreach (String artist in artists)
            {
                //Get the top tracks for each artist
                XmlDocument artistXML = new XmlDocument();
                try
                {
                    artistXML.Load("http://ws.audioscrobbler.com/2.0/?method=artist.gettoptracks&artist=" + artist
                        + "&limit=" + trackCount + "&page=" + page + "&api_key=" + Global.LASTKEY);
                    XmlNodeList lastList = artistXML.GetElementsByTagName("track");
                    for (int j = 0; j < trackCount; j++)
                    {
                        trackArray.Add(new TrackInfo(HttpUtility.HtmlDecode(artist), HttpUtility.HtmlDecode(lastList[j]["name"].InnerXml), "", "", trackArray.Count, Global.MLOVEDA));
                    }
                }
                catch (Exception ex)
                {
                    //let's see what happens if we keep going
                    /*
                    return trackArray; //Just break, don't want to miss all the lovely tracks we have already found
                     */
                }
            }
            return trackArray;
        }

        public List<TrackInfo> setIndexes(List<TrackInfo> trackArray)
        {
            for (int i = 0; i < trackArray.Count; i++)
            {
                trackArray[i].setIndex(i);
            }
            return trackArray;
        }

        public List<TrackInfo> setPlayCounts(List<TrackInfo> trackArray)
        {
            for (int i = 0; i < trackArray.Count; i++)
            {
                lastXML.Load("http://ws.audioscrobbler.com/2.0/?method=track.getinfo&api_key=" + Global.LASTKEY + "&artist=" + HttpUtility.UrlEncode(trackArray[i].getArtist()) + "&track=" + HttpUtility.UrlEncode(trackArray[i].getTrackName())
                    + "&username=" + Properties.Settings.Default.username);
                if (lastXML["lfm"]["track"]["userplaycount"] != null)
                {
                    trackArray[i].setPlayCount(int.Parse(lastXML["lfm"]["track"]["userplaycount"].InnerXml));
                }
                else
                {
                    trackArray[i].setPlayCount(0);
                }
            }
            return trackArray;
        }

        public String getCorrection(String artist, String track) // Returns "E1" if there's an error, otherwise returns artist - track
        {
            XmlDocument correctionInfo = new XmlDocument();
            try
            {
                correctionInfo.Load("http://ws.audioscrobbler.com/2.0/?method=track.getcorrection&artist=" + artist + "&track=" + track + "&api_key=" + Global.LASTKEY);
            }
            catch (XmlException e)
            {
                return "";
            }

            return correctionInfo["track"]["artist"]["name"].InnerXml + " - " + correctionInfo["track"]["name"].InnerXml;;
        }

        public TrackInfo getCorrectionInfo(TrackInfo theTrack) // Returns "E1" if there's an error, otherwise returns artist - track
        {
            XmlDocument correctionInfo = new XmlDocument();
            try
            {
                correctionInfo.Load("http://ws.audioscrobbler.com/2.0/?method=track.getcorrection&artist=" + theTrack.getArtist() + "&track=" + theTrack.getTrackName() + "&api_key=" + Global.LASTKEY);
            }
            catch (XmlException e)
            {
                return theTrack;
            }

            theTrack.setArtist(correctionInfo["track"]["artist"]["name"].InnerXml);
            theTrack.setTrackName(correctionInfo["track"]["name"].InnerXml);

            return theTrack;
        }

        public String authenticate()
        {
            //TODO: Store user's key securely
            XmlDocument getToken = new XmlDocument();

            String token = "";

            //Let's get a token for the user

            try
            {
                getToken.Load("http://ws.audioscrobbler.com/2.0/?method=auth.gettoken&api_key=" + Global.LASTKEY);
            }
            catch(Exception ex)
            {
                throw ex;
            }

            token = getToken["lfm"]["token"].InnerXml;

            // Send the user to the token page

            authForm theAuthForm = new authForm("http://www.last.fm/api/auth/?api_key=" + Global.LASTKEY + "&token=" + token);
            theAuthForm.ShowDialog();

            /*
            System.Diagnostics.Process.Start("http://www.last.fm/api/auth/?api_key=" + Global.LASTKEY + "&token=" + token);
             */

            XmlDocument getSession = new XmlDocument();

            //We've got the user to authenticate the token let's construct our sig

            String sig = ("api_key" + Global.LASTKEY + "methodauth.getSessiontoken" + token + Global.SECRET);

            //Use an MD5 of the sig to get the session

            try
            {
                getSession.Load("http://ws.audioscrobbler.com/2.0/?method=auth.getSession&api_key=" + Global.LASTKEY + "&token=" + token + "&api_sig=" + calculateMD5Hash(sig));
            }
            catch (Exception ex)
            {
                throw ex;
            }

            if (getSession["lfm"].Attributes[0].Value == "failed") //if it failed
            {
                return getSession["lfm"]["error"].Attributes[0].Value;
            }

            String sessionKey = getSession["lfm"]["session"]["key"].InnerXml;

            return sessionKey;
        }

        public HashSet<String> getArtists()
        {
            return artists;
        }

        public void setArtists(HashSet<String> iArtists)
        {
            artists = iArtists;
        }


        //Hash calculator taken from http://blogs.msdn.com/b/csharpfaq/archive/2006/10/09/how-do-i-calculate-a-md5-hash-from-a-string_3f00_.aspx
        //Encoding changed to UTF8 for last.fm's benefit
        public string calculateMD5Hash(string input)
        {
            // step 1, calculate MD5 hash from input
            MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] inputBytes = System.Text.Encoding.UTF8.GetBytes(input);
            byte[] hash = md5.ComputeHash(inputBytes);

            // step 2, convert byte array to hex string
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("x2"));
            }
            return sb.ToString();
        }

    }
}
